/*	
	stencil - action script 3 isometric game engine.

	http://code.google.com/p/stencilas3/
	
	Copyright (c) 2010 Max Zhuravkov, All Rights Reserved.

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
	of the Software, and to permit persons to whom the Software is furnished to do
	so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/
package stencil.core.engine 
{
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import stencil.core.errors.ArgumentIsNullError;
	
	
	/**
	 * 
	 * @author Max Zhuravkov
	 */
	public class Box {
		
		/**
		 * x-dimension position of this Box.
		 */
		public var x:Number;
		
		/**
		 * y-dimension position of this Box.
		 */
		public var y:Number;
		
		/**
		 * z-dimension position of this Box.
		 */
		public var z:Number;
		
		
		/**
		 * x-dimension  size of this Box.
		 */
		public var dx:Number;
		
		/**
		 * y-dimension  size of this Box.
		 */
		public var dy:Number;
		
		/**
		 * z-dimension  size of this Box.
		 */
		public var dz:Number;
		
		private var _rect:Rectangle = new Rectangle();
		
		//----------------------------------------------------------------
		// Constructor
		//----------------------------------------------------------------
		
		public function Box(x:Number = 0, y:Number = 0, z:Number = 0, dx:Number = 1, dy:Number = 1, dz:Number = 1) {
			this.x = x;
			this.y = y;
			this.z = z;
			
			this.dx = dx;
			this.dy = dy;
			this.dz = dz;
		}
		
		//------------------------------------------------------------------------------------------------
		// Public properties
		//------------------------------------------------------------------------------------------------
		
		public function get rect():Rectangle {
			this._rect.x = this.x;
			this._rect.y = this.y;
			this._rect.width = this.dx;
			this._rect.height = this.dy;
			return this._rect;
		}
		
		//------------------------------------------------------------------------------------------------
		// Public methods
		//------------------------------------------------------------------------------------------------
		
		public function toString():String {
			return "[Box x=" + this.x + " y=" + this.y + " z=" + this.z + " dx=" + this.dx + " dy=" + this.dy + " dz=" + this.dz + "]" ;
		}
		
		
		/**
		 * Determines whether sizes of this Box object measures are equal to measures of the passed box. This method compares dx, dy and dz properties of two object.
		 * 
		 * @param	b	the Box object.
		 * 
		 * @return
		 */
		public function sizesEqual(b:Box):Boolean {
			if (b == this) {
				return true;
			}
			var ddx:Number = Math.abs(this.dx - b.dx);
			var ddy:Number = Math.abs(this.dy - b.dy);
			var ddz:Number = Math.abs(this.dz - b.dz);
			
			return ddx <= 0.005 && ddy <= 0.005 && ddz <= 0.005;
		}
		
		/**
		 * Determines whether this Box object position and measures are equal to the given box. This method compares x, y, z, dx, dy amd dz of both objects.
		 * 
		 * @param	b	the Box object.
		 * 
		 * @return	
		 */
		public function equals(b:Box):Boolean {
			if (b == this) {
				return true;
			}
			
			var dx:Number = Math.abs(this.x - b.x);
			var dy:Number = Math.abs(this.y - b.y);
			var dz:Number = Math.abs(this.z - b.z);
			
			var ddx:Number = Math.abs(this.dx - b.dx);
			var ddy:Number = Math.abs(this.dy - b.dy);
			var ddz:Number = Math.abs(this.dz - b.dz);
			
			return dx <= 0.005 && dy <= 0.005 && dz <= 0.005 && ddx <= 0.005 && ddy <= 0.005 && ddz <= 0.005;
			
			//return this.x == a.x && this.y == a.y && this.z == a.z && this.dx == a.dx && this.dy == a.dy && this.dz == a.dz;
		}
		
		/**
		 * Create copy of this Box.
		 * 
		 * @return
		 */
		public function clone():Box {
			return new Box(this.x, this.y, this.z, this.dx, this.dy, this.dz);
		}
		
		/**
		 * Adjusts coordinates of this Box object by specified amounts.
		 */
		public function offset(dx:Number, dy:Number, dz:Number):void {
			this.x += dx;
			this.y += dy;
			this.z += dz;
		}
		
		/**
		 * Adjusts coordinates of this Box object using the given Position object.
		 */
		public function offsetPosition(position:Position):void {
			if (!position) {
				throw new ArgumentIsNullError("position")
			}
			this.offset(position.x, position.y, position.y);
		}
		
		/**
		 * Adjusts coordinates of this Box object using the given Point object (change of z coordinate is omitted).
		 */
		public function offsetPoint(point:Point):void {
			if (!point) {
				throw new ArgumentIsNullError("point")
			}
			this.offset(point.x, point.y, 0);
		}
		
		/**
		 * Increases the size of this Box object by the specified amounts.
		 */
		public function inflate(dx:Number, dy:Number, dz:Number):void {
			this.x += dx;
			this.y += dy;
			this.z += dz;
			
			this.dx += dx;
			this.dy += dy;
			this.dz += dz;
		}
		
		/**
		 * Increases the size of this Area object..
		 */
		public function inflatePosition(position:Position):void {
			if (!position) {
				throw new ArgumentIsNullError("position");
			}
			this.inflate(position.x, position.y, position.z);
		}
		
		/**
		 * Increases the size of this Area object (z/dz increase is omitted).
		 */
		public function inflatePoint(point:Point):void {
			if (!point) {
				throw new ArgumentIsNullError("point");
			}
			this.inflate(point.x, point.y, 0);
		}
		
		/**
		 * Determines whether this Area object contains the given point or not.
		 */
		public function contains(x:Number, y:Number, z:Number):Boolean {
			return x >= this.x && x < this.dx && y >= this.y && y < this.y && z >= this.z && z < this.dz;
		}
		
		/**
		 * Determines whether this Area object contains the given point or not (z-coordinate test is omitted).
		 */
		public function containsPoint(point:Point):Boolean {
			if (!point) {
				throw new ArgumentIsNullError("point")
			}
			return point.x >= this.x && point.x < this.dx && point.y >= this.y && point.y < this.y
		}
		
		/**
		 * Determines whether the given box is contained within this Box object or not.
		 * 
		 * @param	b	the box to test.
		 * 
		 * @return
		 */
		public function containsBox(b:Box):Boolean {
			
			return b.x >= this.x && b.x + b.dx < this.x + this.dx && 
					b.y >= this.y && b.y + b.dy < this.y + this.dy &&
					b.z >= this.z && b.z + b.dz < this.z + this.dz;
			
		}
		
		/**
		 * Determines whether the given box if it were placed at the specified position is contained within this Box object or not.
		 * 
		 * @param	b	the box to test.
		 * @param	p	the position.
		 * 
		 * @return
		 */
		public function containsBoxAt(b:Box, p:Position):Boolean {
			return p.x >= this.x && p.x + b.dx < this.x + this.dx && 
					p.y >= this.y && p.y + b.dy < this.y + this.dy &&
					p.z >= this.z && p.z + b.dz < this.z + this.dz;
			
		}
		
		/**
		 * Determines whether this Box object contains the given point or not.
		 */
		public function containsPosition(position:Position):Boolean {
			if (!position) {
				throw new ArgumentIsNullError("position");
			}
			return this.contains(position.x, position.y, position.z);
		}
		
		/**
		 * Determines whether the passed object intersects with this Box object or not.
		 * 
		 * @param	box	the Box object to test intersection with.
		 * 
		 * @return
		 */
		public function intersects(box:Box):Boolean {
			if (!box) {
				throw new ArgumentIsNullError("box");
			}
			
			return this.checkIntersection(this, box) ? true : this.checkIntersection(box, this);
		}
		
		/**
		 * If the box passed in the  'boxToIntercept' parameter intersects with this Box object returns intersection. Otherwise returns an empty Box object. 
		 * 
		 * @param	boxToIntercept	the Box object.
		 * 
		 * @return
		 */
		public function intersection(boxToIntercept:Box):Box {
			if (!boxToIntercept) {
				throw new ArgumentIsNullError("boxToIntercept");
			}
			
			if (this.equals(boxToIntercept)) {
				return this.clone();
			}
			
			if (!this.intersects(boxToIntercept)) {
				return new Box();
			}
			
			var ix:Number = 0;
			var iy:Number = 0;
			var iz:Number = 0;
			var iw:Number = 0;
			var ih:Number = 0;
			var id:Number = 0;
			var d:Number = 0;
			var amax:Number = 0;
			var bmax:Number = 0;
			
			
			var a1:Box = this;
			var b1:Box  = boxToIntercept;
			
			if (this.x > boxToIntercept.x) {
				a1 = boxToIntercept;
				b1 = this;
			}
			
			amax = (a1.dx + a1.x)
				
			if (a1.x <= b1.x && b1.x < amax) {
				d = amax - b1.x;
				bmax = (b1.dx + b1.x)
				if (bmax < amax) {
					d -= amax - bmax
				}
				ix = b1.x;
				iw = d;
			}else {
				ix = 0;
				iw = 0;
			}
			
			if (this.y > boxToIntercept.y) {
				a1 = boxToIntercept;
				b1 = this;
			}else {
				a1 = this;
				b1 = boxToIntercept;
			}
			
			amax = (a1.dy + a1.y)
			
			if (a1.y <= b1.y && b1.y < amax ) {
				d = amax - b1.y;
				bmax = (b1.dy + b1.z)
				if (bmax < amax) {
					d -= amax - bmax
				}
				iy = b1.y;
				ih = d;
			}else {
				iy = 0;
				ih = 0;
			}
			
			if (this.z > boxToIntercept.z) {
				a1 = boxToIntercept;
				b1 = this;
			}else {
				a1 = this;
				b1 = boxToIntercept;
			}
			
			
			amax = (a1.dz + a1.z)
			
			if (a1.z <= b1.z && b1.z < amax) {
				d = amax - b1.z;
				bmax = (b1.dz + b1.z)
				if (bmax < amax) {
					d -= amax - bmax
				}
				iz = b1.z;
				id = d;
			}else {
				iz = 0;
				id = 0;
			}
			
			return new Box(ix, iy, iz, iw, ih, id);
		}
		
		
		//----------------------------------------------------------------
		// Private methods
		//----------------------------------------------------------------
		
		private function checkIntersection(a:Box, b:Box):Boolean {
			if (a.dx <= 0 || a.dy <= 0 || a.dz <= 0 ) {
				return false;
			}
			
			return a.x >= b.x && a.x < b.x + b.dx && 
					a.y >= b.y && a.y < b.y + b.dy && 
					a.z >= b.z && a.z < b.z + b.dz;
		}
		
	}

}